using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Xml;
using System.IO;
using Microsoft.SharePoint.Administration;
using System.Diagnostics;

namespace TST.SharePointObjects.SecurityBluePrint
{
    public class SecurityBluePrint
    {
        private const string BLUEPRINTLIBRARYNAME = "Blueprints";
        private const string REPORTSITEFIELD = "ReportSite";
        private const string REPORTWEBFIELD = "ReportWeb";
        private const string REPORTTYPEFIELD = "ReportType";
        private const string CHECKSUMFIELD = "Checksum";
        private const string FULLREPORT = "Full Report";
        private const string NOCHANGES = "No Changes";
        private string _librarySiteUrl;

        public SecurityBluePrint(String librarySiteUrl)
        {
            _librarySiteUrl = librarySiteUrl;
        }

        private Boolean UseCentralStorage
        {
            get
            {
                return !String.IsNullOrEmpty(_librarySiteUrl) && _librarySiteUrl.IndexOf("://") > 0;
            }
        }

        public String EndPoints
        {
            get;
            set;
        }

        public string Run(SPWeb web)
        {
            string resultUrl = web.Url;
            if (web == null)
                return resultUrl;
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                SPSite site = web.Site;
                using (SPWeb root = site.RootWeb)
                {
                    SPWeb storageWeb = null;
                    Boolean dispose = false;
                    if (!String.IsNullOrEmpty(_librarySiteUrl))
                    {
                        if (UseCentralStorage)
                        {
                            using (SPSite storageSite = new SPSite(_librarySiteUrl))
                            {
                                storageWeb = storageSite.OpenWeb();
                                dispose = true;
                                if (!storageWeb.Exists)
                                {
                                    storageWeb = root;
                                    dispose = false;
                                }
                            }
                        }
                        else
                        {
                            storageWeb = site.OpenWeb(_librarySiteUrl);
                            dispose = true;
                            if (!storageWeb.Exists)
                            {
                                storageWeb = root;
                                dispose = false;
                            }
                        }
                    }
                    else
                    {
                        storageWeb = web;
                        dispose = false;
                    }

                    SPList bluePrintLibrary = null;
                    storageWeb.AllowUnsafeUpdates = true;
                    try
                    {
                        bluePrintLibrary = EnsureBlueprintLibrary(storageWeb, bluePrintLibrary, site, web);

                        using (MemoryStream stream = new MemoryStream())
                        {
                            XmlWriterSettings settings = new XmlWriterSettings();
                            settings.Indent = true;
                            settings.NewLineOnAttributes = true;
                            settings.Encoding = Encoding.UTF8;
                            XmlWriter writer = XmlWriter.Create(stream, settings);
                            writer.WriteStartDocument();
                            try
                            {
                                writer.WriteStartElement("Security");
                                writer.WriteStartElement("Web");
                                HandleWeb(web, writer);
                                writer.WriteEndElement(); // web
                                writer.WriteEndElement(); // security
                            }
                            finally
                            {
                                writer.WriteEndDocument();
                                writer.Flush();
                                resultUrl = SaveBlueprint(site, web, stream, bluePrintLibrary);
                                site.Dispose();
                                writer.Close();
                            }
                        }
                    }
                    finally
                    {
                        if (dispose && storageWeb != null)
                        {
                            storageWeb.Dispose();
                        }
                    }
                }
            });
            return resultUrl;
        }

        private void HandleWeb(SPWeb web, XmlWriter writer)
        {
            writer.WriteAttributeString("ServerRelativeUrl", web.ServerRelativeUrl);
            writer.WriteAttributeString("Title", web.Title);
            writer.WriteAttributeString("AnonymousPermMask64", web.AnonymousPermMask64.ToString());
            ulong mask = (ulong)web.AnonymousPermMask64;
            writer.WriteAttributeString("AnonymousPermMask64_Mask", mask.ToString());
            writer.WriteAttributeString("AnonymousState", web.AnonymousState.ToString());
            writer.WriteAttributeString("HasUniqueRoleAssignments", web.HasUniqueRoleAssignments.ToString());
            writer.WriteAttributeString("HasUniqueRoleDefinitions", web.HasUniqueRoleDefinitions.ToString());
            writer.WriteAttributeString("RequestAccessEmail", web.RequestAccessEmail);
            writer.WriteAttributeString("RequestAccessEnabled", web.RequestAccessEnabled.ToString());

            // Associated groups
            writer.WriteStartElement("AssociatedGroups");
            if (web.AssociatedVisitorGroup != null)
                writer.WriteAttributeString("Visitor", web.AssociatedVisitorGroup.Name);
            else
                writer.WriteAttributeString("Visitor", String.Empty);
            if (web.AssociatedMemberGroup != null)
                writer.WriteAttributeString("Member", web.AssociatedMemberGroup.Name);
            else
                writer.WriteAttributeString("Member", String.Empty);
            if (web.AssociatedOwnerGroup != null)
                writer.WriteAttributeString("Owner", web.AssociatedOwnerGroup.Name);
            else
                writer.WriteAttributeString("Owner", String.Empty);

            if (WebIsEndPoint(web))
            {
                writer.WriteAttributeString("BluePrintEndPoint", true.ToString());
            }

            foreach (SPGroup group in web.AssociatedGroups)
            {
                writer.WriteStartElement("Associatedgroup");
                writer.WriteAttributeString("Name", group.Name);
                writer.WriteEndElement(); // associatedgroup
            }
            writer.WriteEndElement(); // associatedgroups

            // Role Assignments
            writer.WriteStartElement("RoleAssignments");
            HandleRoleAssignments(web, writer);
            writer.WriteEndElement(); // RoleAssignments

            // Role Definitions
            writer.WriteStartElement("RoleDefinitions");
            HandleRoleDefinitions(web, writer);
            writer.WriteEndElement(); // RoleDefinitions

            // Lists
            writer.WriteStartElement("Lists");
            HandleLists(web, writer);
            writer.WriteEndElement(); // Lists

            // Features
            writer.WriteStartElement("WebFeatures");
            HandleWebFeatures(web, writer);
            writer.WriteEndElement(); // WebFeatures

            // Write the specific root web settings.
            using (SPSite site = web.Site)
            {
                using (SPWeb rootWeb = site.RootWeb)
                {
                    if (rootWeb.ID == web.ID)
                    {
                        HandleSiteAdminstrators(rootWeb, writer);
                        HandleSiteGroups(rootWeb, writer);
                        HandleSiteCollectionSettings(site, writer);

                        // Features
                        writer.WriteStartElement("SiteFeatures");
                        HandleSiteFeatures(site, writer);
                        writer.WriteEndElement(); // SiteFeatures
                    }
                }
            }

            writer.WriteStartElement("Webs");
            if (!WebIsEndPoint(web))
            {
                for (int i = 0; i < web.Webs.Count; i++)
                {
                    SPWeb subWeb = web.Webs[i];
                    writer.WriteStartElement("Web");
                    try
                    {
                        HandleWeb(subWeb, writer);
                    }
                    finally
                    {
                        writer.WriteEndElement(); // web
                        subWeb.Dispose();
                    }
                }
            }
            writer.WriteEndElement(); // webs
        }

        private bool WebIsEndPoint(SPWeb web)
        {
            string[] endPoints = EndPoints.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (String ep in endPoints)
            {
                String endPoint = ep;
                if (!endPoint.StartsWith("/"))
                    endPoint = "/" + endPoint;
                if (web.ServerRelativeUrl.ToLower().EndsWith(endPoint.ToLower()))
                    return true;
            }
            return false;
        }

        private void HandleWebFeatures(SPWeb web, XmlWriter writer)
        {
            foreach (SPFeature feature in web.Features)
            {
                writer.WriteStartElement("WebFeature");
                writer.WriteAttributeString("ID", feature.DefinitionId.ToString("B"));
                if (feature.Definition != null)
                {
                    writer.WriteAttributeString("DisplayName", feature.Definition.DisplayName);
                }
                writer.WriteEndElement(); // WebFeature
            }
        }

        private void HandleSiteFeatures(SPSite site, XmlWriter writer)
        {
            foreach (SPFeature feature in site.Features)
            {
                writer.WriteStartElement("SiteFeature");
                writer.WriteAttributeString("ID", feature.DefinitionId.ToString("B"));
                if (feature.Definition != null)
                {
                    writer.WriteAttributeString("DisplayName", feature.Definition.DisplayName);
                }
                writer.WriteEndElement(); // SiteFeature
            }
        }

        private void HandleLists(SPWeb web, XmlWriter writer)
        {
            foreach (SPList list in web.Lists)
            {
                writer.WriteStartElement("List");
                writer.WriteAttributeString("Name", list.RootFolder.Name);
                writer.WriteAttributeString("Title", list.Title);
                writer.WriteAttributeString("AllowEveryoneViewItems", list.AllowEveryoneViewItems.ToString());
                writer.WriteAttributeString("AnonymousPermMask64", list.AnonymousPermMask64.ToString());
                ulong mask = (ulong)list.AnonymousPermMask64;
                writer.WriteAttributeString("AnonymousPermMask64_Mask", mask.ToString());
                writer.WriteAttributeString("HasUniqueRoleAssignments", list.HasUniqueRoleAssignments.ToString());
                writer.WriteAttributeString("ReadSecurity", list.ReadSecurity.ToString());
                writer.WriteAttributeString("RequestAccessEnabled", list.RequestAccessEnabled.ToString());
                writer.WriteAttributeString("WriteSecurity", list.WriteSecurity.ToString());
                HandleRoleAssignments(list, writer);
                writer.WriteEndElement(); // List
            }
        }

        private void HandleSiteCollectionSettings(SPSite site, XmlWriter writer)
        {
            writer.WriteStartElement("SiteCollectionSettings");
            writer.WriteAttributeString("ApplicationRightsMask", site.ApplicationRightsMask.ToString());
            ulong mask = (ulong)site.ApplicationRightsMask;
            writer.WriteAttributeString("ApplicationRightsMask_Mask", mask.ToString());
            writer.WriteAttributeString("IISAllowsAnonymous", site.IISAllowsAnonymous.ToString());
            writer.WriteAttributeString("Impersonating", site.Impersonating.ToString());
            writer.WriteAttributeString("SystemAccount", site.SystemAccount.LoginName);
            writer.WriteAttributeString("LastSecurityModifiedDate", site.LastSecurityModifiedDate.ToString("dd-MM-yyyy HH:mm:ss"));
            writer.WriteEndElement(); // SiteCollectionSettings
        }

        private void HandleSiteGroups(SPWeb rootWeb, XmlWriter writer)
        {
            writer.WriteStartElement("SiteGroups");
            foreach (SPGroup group in rootWeb.SiteGroups)
            {
                writer.WriteStartElement("SiteGroup");
                writer.WriteAttributeString("ID", group.ID.ToString());
                writer.WriteAttributeString("Name", group.Name);
                writer.WriteAttributeString("AllowMembersEditMembership", group.AllowMembersEditMembership.ToString());
                writer.WriteAttributeString("AllowRequestToJoinLeave", group.AllowRequestToJoinLeave.ToString());
                writer.WriteAttributeString("AutoAcceptRequestToJoinLeave", group.AutoAcceptRequestToJoinLeave.ToString());
                writer.WriteAttributeString("OnlyAllowMembersViewMembership", group.OnlyAllowMembersViewMembership.ToString());
                writer.WriteAttributeString("RequestToJoinLeaveEmailSetting", group.RequestToJoinLeaveEmailSetting);
                writer.WriteEndElement(); // SiteGroup
            }
            writer.WriteEndElement(); // SiteGroups
        }

        private void HandleSiteAdminstrators(SPWeb rootWeb, XmlWriter writer)
        {
            writer.WriteStartElement("SiteAdministrators");
            foreach (SPUser admin in rootWeb.SiteAdministrators)
            {
                writer.WriteStartElement("SiteAdministrator");
                writer.WriteAttributeString("ID", admin.ID.ToString());
                writer.WriteAttributeString("Name", admin.Name);
                writer.WriteAttributeString("LoginName", admin.LoginName);
                writer.WriteEndElement(); // SiteAdministrator
            }
            writer.WriteEndElement(); // SiteAdministrators
        }

        private void HandleRoleDefinitions(SPWeb web, XmlWriter writer)
        {
            if (web.HasUniqueRoleDefinitions)
            {
                foreach (SPRoleDefinition definition in web.RoleDefinitions)
                {
                    writer.WriteStartElement("PermissionLevel");
                    writer.WriteAttributeString("ID", definition.Id.ToString());
                    writer.WriteAttributeString("Name", definition.Name);
                    writer.WriteAttributeString("Type", definition.Type.ToString());
                    writer.WriteAttributeString("Permissions", definition.BasePermissions.ToString());
                    ulong mask = (ulong)definition.BasePermissions;
                    writer.WriteAttributeString("Mask", mask.ToString());
                    writer.WriteEndElement(); // PermissionLevel
                }
            }
        }

        private void HandleRoleAssignments(ISecurableObject securedObject, XmlWriter writer)
        {
            if (securedObject.HasUniqueRoleAssignments)
            {
                foreach (SPRoleAssignment assignment in securedObject.RoleAssignments)
                {
                    writer.WriteStartElement("Assignment");
                    writer.WriteAttributeString("MemberID", assignment.Member.ID.ToString());
                    writer.WriteAttributeString("MemberName", assignment.Member.Name);
                    if (assignment.Member is SPUser)
                    {
                        writer.WriteAttributeString("MemberType", "User");
                        writer.WriteAttributeString("LoginName", ((SPUser)assignment.Member).LoginName);
                    }
                    else if (assignment.Member is SPGroup)
                    {
                        writer.WriteAttributeString("MemberType", "Group");
                    }
                    else
                    {
                        writer.WriteAttributeString("MemberType", assignment.Member.GetType().Name);
                    }
                    writer.WriteStartElement("RoleDefinitionBindings");
                    foreach (SPRoleDefinition binding in assignment.RoleDefinitionBindings)
                    {
                        writer.WriteStartElement("Binding");
                        writer.WriteAttributeString("ID", binding.Id.ToString());
                        writer.WriteAttributeString("Name", binding.Name);
                        writer.WriteEndElement(); // Binding
                    }
                    writer.WriteEndElement(); // RoleDefinitionBindings
                    writer.WriteEndElement(); // Assignment
                }
            }
        }

        private string SaveBlueprint(SPSite reportSite, SPWeb reportWeb, MemoryStream report, SPList bluePrintLibrary)
        {
            string result = String.Empty;
            if (bluePrintLibrary == null || reportSite == null || reportWeb == null || report.Length == 0)
                return result;

            // Get the last full report
            String lastReportCheckSum = String.Empty;
            SPQuery query = new SPQuery();
            query.Query = string.Format("<Where><And><Eq><FieldRef Name='{4}' /><Value Type='Text'>{5}</Value></Eq><And><Eq><FieldRef Name='{0}' /><Value Type='Text'>{1}</Value></Eq><Eq><FieldRef Name='{2}' /><Value Type='Text'>{3}</Value></Eq></And></And></Where>", REPORTTYPEFIELD, FULLREPORT, REPORTWEBFIELD, reportWeb.ServerRelativeUrl, REPORTSITEFIELD, reportSite.Url);
            query.Query += "<OrderBy><FieldRef Name=\"Modified\" Ascending=\"FALSE\" /></OrderBy>";
            query.ViewAttributes = "Scope=\"Recursive\"";
            query.RowLimit = 1;
            SPListItemCollection lastFullReports = bluePrintLibrary.GetItems(query);
            if (lastFullReports.Count == 1 && lastFullReports[0][CHECKSUMFIELD] != null)
            {
                lastReportCheckSum = lastFullReports[0][CHECKSUMFIELD].ToString();
            }

            String sitecollection = reportSite.ServerRelativeUrl.Substring(reportSite.ServerRelativeUrl.LastIndexOf("/") + 1);
            string fileName = String.Format("SecurityBlueprint_{0}_{1}_{2}_{3}.xml", sitecollection, reportWeb.Title, DateTime.Now.ToString("yyyyMMdd"), DateTime.Now.ToString("HHmmss"));
            Trace.WriteLine(String.Format("Write security blueprint to file {0}", fileName));
            fileName = fileName.Replace(":", String.Empty);
            fileName = fileName.Replace(" ", String.Empty);

            SPFolder storageFolder = null;
            if (UseCentralStorage)
            {
                storageFolder = null;
                String folderName = reportSite.ServerRelativeUrl.Replace("/", "_");
                foreach (SPFolder folder in bluePrintLibrary.RootFolder.SubFolders)
                {
                    if (folder.Name.Equals(folderName))
                    {
                        storageFolder = folder;
                        break;
                    }
                }
                if (storageFolder == null)
                {
                    storageFolder = bluePrintLibrary.RootFolder.SubFolders.Add(folderName);
                }
            }

            if (storageFolder == null)
                storageFolder = bluePrintLibrary.RootFolder;

            byte[] content = report.ToArray();
            System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] checksum = md5.ComputeHash(content);
            String checkMD5 = BitConverter.ToString(checksum);

            if (String.IsNullOrEmpty(lastReportCheckSum))
            {
                SPFile newBlueprint = storageFolder.Files.Add(fileName, content);
                newBlueprint.Item[REPORTSITEFIELD] = reportSite.Url;
                newBlueprint.Item[REPORTWEBFIELD] = reportWeb.ServerRelativeUrl;
                newBlueprint.Item[REPORTTYPEFIELD] = FULLREPORT;
                newBlueprint.Item[CHECKSUMFIELD] = checkMD5;
                newBlueprint.Item.Update();
            }
            else
            {
                if (checkMD5.Equals(lastReportCheckSum))
                {
                    byte[] noChangesContent = GetNoChangesContent();
                    SPFile newBlueprint = storageFolder.Files.Add(fileName, noChangesContent);
                    newBlueprint.Item[REPORTSITEFIELD] = reportSite.Url;
                    newBlueprint.Item[REPORTWEBFIELD] = reportWeb.ServerRelativeUrl;
                    newBlueprint.Item[REPORTTYPEFIELD] = NOCHANGES;
                    newBlueprint.Item[CHECKSUMFIELD] = String.Empty;
                    newBlueprint.Item.Update();
                }
                else
                {
                    SPFile newBlueprint = storageFolder.Files.Add(fileName, content);
                    newBlueprint.Item[REPORTSITEFIELD] = reportSite.Url;
                    newBlueprint.Item[REPORTWEBFIELD] = reportWeb.ServerRelativeUrl;
                    newBlueprint.Item[REPORTTYPEFIELD] = FULLREPORT;
                    newBlueprint.Item[CHECKSUMFIELD] = checkMD5;
                    newBlueprint.Item.Update();
                }
            }
            result = bluePrintLibrary.DefaultViewUrl;

            return result;
        }

        private byte[] GetNoChangesContent()
        {
            byte[] result;
            using (MemoryStream stream = new MemoryStream())
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.NewLineOnAttributes = true;
                settings.Encoding = Encoding.UTF8;
                XmlWriter writer = XmlWriter.Create(stream, settings);
                writer.WriteStartDocument();
                try
                {
                    writer.WriteStartElement("Security");
                    writer.WriteEndElement(); // security
                }
                finally
                {
                    writer.WriteEndDocument();
                    writer.Flush();
                    result = stream.ToArray();
                    writer.Close();
                }
            }
            return result;
        }

        private SPList EnsureBlueprintLibrary(SPWeb storageWeb, SPList bluePrintLibrary, SPSite reportSite, SPWeb reportWeb)
        {
            foreach (SPList list in storageWeb.Lists)
            {
                if (String.Compare(list.RootFolder.Name, BLUEPRINTLIBRARYNAME, true) == 0)
                {
                    bluePrintLibrary = list;
                    break;
                }
            }
            if (bluePrintLibrary == null)
            {
                Guid newID = storageWeb.Lists.Add(BLUEPRINTLIBRARYNAME, String.Empty, SPListTemplateType.DocumentLibrary);
                bluePrintLibrary = storageWeb.Lists[newID];
                bluePrintLibrary.Title = "Security blue prints";
                bluePrintLibrary.Update();
            }
            if (!bluePrintLibrary.Fields.ContainsField(REPORTSITEFIELD))
            {
                string newFieldName = bluePrintLibrary.Fields.Add(REPORTSITEFIELD, SPFieldType.Text, true);
                SPField newField = bluePrintLibrary.Fields[newFieldName];
                newField.Title = "Site collection Url";
            }
            if (!bluePrintLibrary.Fields.ContainsField(REPORTWEBFIELD))
            {
                string newFieldName = bluePrintLibrary.Fields.Add(REPORTWEBFIELD, SPFieldType.Text, true);
                SPField newField = bluePrintLibrary.Fields[newFieldName];
                newField.Title = "Site Url";
            }
            if (!bluePrintLibrary.Fields.ContainsField(REPORTTYPEFIELD))
            {
                string newFieldName = bluePrintLibrary.Fields.Add(REPORTTYPEFIELD, SPFieldType.Text, true);
                SPField newField = bluePrintLibrary.Fields[newFieldName];
                newField.Title = "Report type";
            }
            if (!bluePrintLibrary.Fields.ContainsField(CHECKSUMFIELD))
            {
                string newFieldName = bluePrintLibrary.Fields.Add(CHECKSUMFIELD, SPFieldType.Text, true);
                SPField newField = bluePrintLibrary.Fields[newFieldName];
                newField.Title = "Checksum";
            }
            // Update the view
            SPView view = bluePrintLibrary.DefaultView;
            if (!view.ViewFields.Exists(REPORTWEBFIELD) ||
                !view.ViewFields.Exists(REPORTTYPEFIELD))
            {
                view.ViewFields.DeleteAll();
                view.ViewFields.Add("DocIcon");
                view.ViewFields.Add("LinkFilename");
                view.ViewFields.Add(REPORTSITEFIELD);
                view.ViewFields.Add(REPORTWEBFIELD);
                view.ViewFields.Add(REPORTTYPEFIELD);
                view.ViewFields.Add("Modified");
                view.Query = "<OrderBy><FieldRef Name=\"Modified\" Ascending=\"FALSE\" /></OrderBy>";
                view.Scope = SPViewScope.Recursive;
                view.Update();
            }
            return bluePrintLibrary;
        }

    }
}
